# yield表达式 代码级别控制保存运行状态 
# 语法： yield [表达式] 或 yield from [表达式]
#       如果表达式有返回值，则在调用next(generator)时将获取到返回值
#       yield from 主要用于迭代子迭代器
# yield只用于generator function的body体中
# 使用yield的函数不再是普通的函数，而是generator function函数
# 通过 函数名() 调用函数会返回一个generator对象，而不是执行函数体内的代码
# generator对象实现了__next__特殊方法，所以使用内建函数next(g)调用
# 3.5.2之前yield是python中实现协程的内建方法
# 之后python为协程设计了async和await以区别于generator对象，返回coroutine对象(内部基于generator)

def echo(value=None):
    # 第一次使用内建函数调用next(generator)时会打印该句
    print('Execution start when next() is called for the first time')
    try:
        while True:
            try: 
                # 第一次传参时或通过generator.send(value)时value或接收到值并赋值给value
                value = yield value # yield后面的值会作为返回值返回给next(g) 或者 generator.send的调用方
            except TypeError as e: 
                # 当通过generator.throw(type, value)时，出现异常捕获并赋值给value
                # value会作为throw()的返回值返给调用方
                # 如果此处没有捕获异常将会将异常抛给调用方
                value = e
    finally:
        # 当内层while循环退出时，调用generator.close()会执行到该部分代码
        print('Clean up when close() is called.')

def f1_generator_obj():
    import inspect # 导入inspect模块查看generator对象的状态
    g = echo('hello') # 第一次调用传递参数
    print('inspect ->', inspect.getgeneratorstate(g))
    print(g, type(g))
    print(next(g)) # 启动generator，到达第一个yield位置挂起，并返回yield值
    print('inspect ->', inspect.getgeneratorstate(g))
    print(next(g)) # 再次调用，恢复yield后代码继续执行，此时没有传值，value为None，yield返回值也是None
    print('inspect ->', inspect.getgeneratorstate(g))
    print(g.send('world')) # 传值给generator，并返回yield的返回值给调用者也就是此处打印的值
    # 传递异常给generator，由于捕获了异常给value, yield返回的value=e, error此处被打印
    # 如果yield未捕获抛出的异常，此处将会抛出异常，后面的代码就不执行了
    print(g.throw(TypeError, 'oops')) 
    print('inspect ->', inspect.getgeneratorstate(g))
    print('close ->',g.close()) # 停止generator，close关闭跳出循环执行finally代码，后续
    print('inspect ->', inspect.getgeneratorstate(g))
    # print(next(g)) # 已停止的generator，再次调用抛出StopIteration错误



# yield from 表达式，子迭代器
# 子迭代器设计用于将大的迭代拆分为多个小的迭代器，像大函数拆分小函数一样使用
def simple_yield_from(x):
    yield from range(x, 0, -1) # 生成倒叙列表
    yield from range(x) # 生成正序列表
    # 等同于 for i in range(x): 
    #           yield i

def f2_yield_from():
    g = simple_yield_from(5)
    print(g) # generator对象
    # print(next(g), next(g)) # 持续执行第一个yield获取5，4，3...
    print(list(g)) # 直接通过list迭代执行generator, 直到StopIteration, 两个yield全部执行完退出

# 练习：计算累加结果，并记录结果
def accumlate():
    ''' 对接收到的值进行累加，直到出现None值 '''
    s = 0 # sum存储累加值
    while 1:
        n = yield # 接收传值
        if n is None: # 如果传递None，结束本次累加并返回累加值
            return s
        s += n # 累加

def recordValue(his:list):
    ''' 
        记录每次累加的值
            参数：
                his : 列表 list
    '''
    while 1:
        s = yield from accumlate() # 计算累加
        his.append(s) # 计算结果存储到列表

def f3_sub_generator():
    his = [] # 记录每次累加的值
    g = recordValue(his) # 获取生成器
    next(g) # 启动生成器，定位到第一个yield位置
    for i in range(4): # 发送0，1，2，3数字进行累加
        g.send(i)
    g.send(None) # 结束本次累加，执行his.append保存结果
    for i in range(4, 6): # 发送4，5数字进行第二组累加
        g.send(i)
    g.send(None) # 结束本次累加
    print('accumlate ->', his)
    g.close() # 关闭生成器


import time

# yield实现的交替打印， yield会保存当前运行状态 一般用于迭代器中
# generator 函数 存在yield表达式的函数就是generator function
def p1():
    for i in range(11):
        yield # 保存当前运行状态 交出执行权限，类似于cpu的执行控制，不过yield是代码级别的
        print('p1 print ', i)
        time.sleep(0.05) # 睡眠0.05秒

def p2():
    g = p1() # 获取generator对象,generator用于控制generator函数的执行, 此处不再是调用函数
    # help(g)
    next(g) # next(iterable) 对于generator对象的第一调用next，为start开始generator函数的执行找到第一个yield表达式位置
    for j in range(10):
        print('p2 print', j)
        time.sleep(0.05)
        # 继续执行yield表达式之后的代码段，直到到达下一个yield表达式(或者循环中到达stopIteration条件)
        next(g) 
    g.close()

# 如果注释掉 yield 和 next，则串行执行， 先执行玩f1在执行f2函数
def f4_print_by_step():
    p2()

# 通过yield实现的生产者-消费者 是python种协程的一个实现
def consumer():
    ''' consumer consume data'''
    while True:
        x = yield # 接收generator send发送的数据
        time.sleep(0.1)
        print('consumer handle data ->', x)

def producer():
    ''' producer produce data '''
    g = consumer()
    next(g) # start generator
    for i in range(6):
        print('producer produce data ->', i)
        # 通过generator给yield传值, 并执行yield后的代码直到下一个yield
        # 如果yield后面有值，send方法返回yield后面的值，如：x=yield y, 则send返回值y
        # send可以用于函数间的值传递
        g.send(i)
        time.sleep(0.1)
    g.close()


# 交替生产-消费
def f5_producer_consumer():
    start = time.time()
    producer()
    end = time.time()
    print('time ->', end - start)


# 使用yield表达式的为generator function， 还有一种不能使用yield的generator expression
# generator expression 返回的也是generator对象
# 语法： 
#   generator_expression = (expression_for) # 必须使用括号包括，并且表达式为for表达式
# generator对象是可迭代对象所以表达式为for表达式
def f6_gen_exp():
    g = (x * y if x > y else x + y for x,y in [(1,2),(3,4)])   # 换成[]九城了列表
    # (x * y for x,y in [(1,2),(3,4)]) for循环求1x2,3x4
    # (x * y if x > y else x + y for x,y in [(1,2),(3,4)]) 增加if表达式判断x>y乘否则加
    # 两层循环 g = (x * y for x in range(1, 3) for y in range(x, x + 3))
    print(g) # generator对象
    print(next(g), next(g)) # 通过内建函数next调用

if __name__ == '__main__':
    # f1_generator_obj()
    # f2_yield_from()
    # f3_sub_generator()
    # f4_print_by_step()
    f5_producer_consumer()